import re

from flask import render_template, redirect, url_for, flash, session, request, current_app, abort, g, jsonify
from flask_mail import Message
from redis import Redis

from . import user_bp
from utils.decorators import login_required
from utils.qiniuyun.tools import upload_image2QINIU
from utils.verification import is_image
from forms.users import UserRegisterForm, UserLoginForm, UserPasswordForm
from models import db
from models.users import User, Role
from apps import mail


@user_bp.route('/')
def index():
    return 'users蓝图'


@user_bp.route('/register', methods=['GET', 'POST'])
def register():
    """用户注册界面以及注册功能视图"""
    form = UserRegisterForm()
    if form.validate_on_submit():
        # 1.参数校验
        # 1.1 检验参数完整性,如果参数不完整，说明请求不是走正常流程从前端发来的，直接返回403
        # 注：这里检验完整性并没有意义，因为使用了flask-wtf表单
        if not all([form.username.data, form.email.data]):
            abort(403)

        # 1.2 校验用户名和邮箱格式是否符合要求
        elif not re.match(r'^[a-zA-Z0-9_-]{5,20}$', form.username.data):
            flash('用户名不符合要求，应该为5-20个字符')
        elif not re.match(r'^[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\.com$', form.email.data):
            flash('邮箱不符合要求，应该为xxx@xxx.com')

        # 1.3 校验用户名和邮箱是否已被使用
        elif User.query.filter_by(username=form.username.data).first():
            flash('用户名已被注册！')
        elif User.query.filter_by(email=form.email.data.lower()).first():
            flash('邮箱已被注册！')

        else:
            # 2.校验通过，数据库中保存注册用户
            try:
                user = User(
                    username=form.username.data,
                    password=form.password.data,
                    email=form.email.data.lower(),
                    role=Role.query.filter_by(default=True).first()
                )
                db.session.add(user)
                db.session.commit()
                flash('注册成功！')

                # 3.1 注册成功,状态保持并重定向到首页
                session['is_login'] = True
                session['username'] = user.username
                return redirect(url_for(current_app.config['INDEX_ENDPOINT']))

            except Exception as e:
                flash('对不起，请重试！')
                current_app.logger.info(e)
        # 3.2 参数校验失败或者写入数据库时出现问题，重定向到当前页面
        return redirect(url_for('users.register'))
    return render_template('register.html', form=form)


@user_bp.route('login', methods=['GET', 'POST'])
def login():
    """用户登录界面以及登录功能视图"""
    form = UserLoginForm()
    if form.validate_on_submit():
        # 参数校验
        user = User.query.filter_by(username=form.username.data).first()
        if not user:
            flash('您输入的用户不存在！')
        elif not user.verify_password(form.password.data):
            flash('您输入的密码不正确！')
        else:
            # 参数校验通过，使用session进行状态保持
            session['is_login'] = True
            session['username'] = user.username
            # 根据用户是否选择“记住我”来设置状态保持的时间，浏览器会话结束或7天
            session.permanent = True if form.remeber_me.data else False

            # 重定向回请求中next参数指定的端点，没有的话默认回到首页
            return redirect(url_for(request.args.get('next', current_app.config['INDEX_ENDPOINT'])))

        # 参数校验不通过，重定向到当前页面重新输入，保持next
        return redirect(url_for('users.login', next=request.args.get('next', current_app.config['INDEX_ENDPOINT'])))

    return render_template('login.html', form=form)


@user_bp.route('logout')
@login_required
def logout():
    """退出登录视图"""
    # 1.清理session
    session.pop('is_login')
    session.pop('username')
    flash('已成功退出登录')
    # 2.重定向到首页
    return redirect(url_for(current_app.config['INDEX_ENDPOINT']))


@user_bp.route('password', methods=['GET', 'POST'])
@login_required
def password():
    """修改密码视图"""
    form = UserPasswordForm()
    if form.validate_on_submit():
        # 1.参数校验
        # 1.1 检验参数完整性,如果参数不完整，说明请求不是走正常流程从前端发来的，直接返回403（这里无意义）
        if not all([form.old_password.data, form.new_password.data, form.new_password2.data]):
            abort(403)
        # 1.2 校验旧密码是否正确，先依据g对象中保存的username来取出当前用户的模型实例
        user = User.query.filter_by(username=g.username).first()
        if not user.verify_password(form.old_password.data):
            flash('旧密码错误！请重新输入')
        else:
            # 2.1校验通过，为用户保存新密码，并重定向到首页
            try:
                user.password = form.new_password.data
                db.session.add(user)
                db.session.commit()
                flash('密码修改成功')
                return redirect(url_for(current_app.config['INDEX_ENDPOINT']))
            except Exception as e:
                flash('对不起，请重试')
                current_app.logger.info(e)
    # 2.2密码校验失败或者更新数据库时出现问题，重定向到当前页面
    return render_template('password.html', form=form)


@user_bp.route('profiles')
@login_required
def profiles():
    """用户个人中心视图"""
    # 查询出当前用户并交给前端
    user = User.query.filter_by(username=g.username).first()
    return render_template('profiles.html', user=user)


@user_bp.route('commit_avatar', methods=['GET', 'POST'])
@login_required
def commit_avatar():
    """上传头像"""
    # 从表单中获取用户上传的头像文件
    image = request.files['avatar']

    # 校验用户上传的对象是否是图片类型
    if not is_image(image):
        flash('请检查您上传的文件是否是图片')
    else:
        # 上传图片到七牛云
        qiniu_file_name = upload_image2QINIU(image.stream.read())
        # 生成图片的URL
        image_url = current_app.config['QINIU_URL'] + qiniu_file_name
        # 在数据库中保存图片URL
        try:
            User.query.filter_by(username=g.username).update({'avatar': image_url})
            db.session.commit()
        except Exception as e:
            flash('对不起，请重试')
            current_app.logger.info(e)

    return redirect(url_for('users.profiles'))


@user_bp.route('send_confirm_mail')
@login_required
def send_confirm_mail():
    """发送认证邮件视图"""
    # 提取参数
    user = User.query.filter_by(username=g.username).first()

    # 校验参数：
    # 如果用户用户组不是“未认证用户”，直接返回403
    if not user.role.name == '未认证用户':
        abort(403)
    redis_conn = Redis(**current_app.config['REDIS_CONFIG_1'])
    # 如果同一个用户发送邮件的请求过于频繁，则拒绝请求
    if redis_conn.get(f'{user.username}_flag') == '1':
        flash('对不起，发送邮件的请求过于频繁，请稍后')
        return redirect(url_for('users.profiles'))

    # 生成邮件内容
    from utils.cryption import TokenWithItsdangerous  # 因为在里面使用了current_app所以放这里防止循环导入
    token = TokenWithItsdangerous.generate(
        username=user.username,
        email=user.email
    )
    link = url_for('users.confirm_email', token=token, _external=True)
    message = Message(
        subject='瑞典开心农场',
        recipients=[f'<{user.email}>', ],
        html=render_template('email/confirm_mail.html', link=link),
        cc=['chneyige@yeah.net', ]
    )
    # todo:发送邮件总是失败，待解决
    # 发送邮件，并且在Redis中保存Token，成功则跳转到“成功发送邮件界面”
    from utils.emails.email_tools import send_mail  # 里面用到了“current_app”，防止循环导入
    try:
        send_mail(message)
    except Exception as e:
        current_app.logger.info(e)
        flash('验证邮件发送失败，请重试')
        return redirect(url_for('users.profiles'))
    # 使用Redis管道确保这两步插入数据操作的原子性
    pipe = redis_conn.pipeline()
    pipe.set(user.username, token, 360)
    pipe.set(f'{user.username}_flag', '1', 60)
    pipe.execute()
    return render_template('email/send_mail_success.html')


@user_bp.route('confirm_email')
@login_required
def confirm_email():
    """验证邮箱视图"""
    # 提取参数
    user = User.query.filter_by(username=g.username).first()
    token = request.args.get('token')

    # 参数校验：
    # 如果用户用户组不是“未认证用户”或者请求中没有查询字符串“token”，直接返回403
    if not user.role.name == '未认证用户' or not token:
        abort(403)
    # Redis中如果没有该用户的Token，说明用户尚未请求发送验证邮件，或者验证邮件已过期，此时跳转到“验证邮箱失败”界面
    redis_conn = Redis(**current_app.config['REDIS_CONFIG_1'])
    token_in_redis = redis_conn.get(user.username).decode()
    if not token_in_redis:
        return render_template('email/confirm_mail_failed.html', err_msg='您尚未请求发送验证邮件，或者验证邮件已过期')

    # 比较查询字符串中的Token和Redis中存储的Token是否一致
    if token == token_in_redis:
        # 一致则修改用户的用户组并且重定向到个人中心界面
        user.role = Role.query.filter_by(name='萌新').first()
        db.session.commit()
        flash(f'邮箱验证成功，您的用户组已经提升为{user.role.name}')
        return redirect(url_for('users.profiles'))
    else:
        # 不一致则跳转到“验证邮箱失败”界面
        return render_template('email/confirm_mail_failed.html', err_msg='您输入的URL有误，请检查后重试')


@user_bp.route('confirm_email_houmen')
@login_required
def confirm_email_houmen():
    """由于发送邮件总是554所以临时设置的用户验证后门"""
    # 提取参数
    user = User.query.filter_by(username=g.username).first()

    # 校验参数：
    err_msg = ''
    redis_conn = Redis(**current_app.config['REDIS_CONFIG_1'])
    # 是不是“未认证用户”
    if not user.role.name == '未认证用户':
        err_msg = '您不是未认证用户'
    # 请求是否过于频繁
    elif redis_conn.get(f'{user.username}_flag') == b'1':
        wait_time = redis_conn.pttl(f'{user.username}_flag') / 1000
        err_msg = f'您的请求过于频繁,请在{wait_time}秒后重试'
    else:
        # 生成邮件内容
        from utils.cryption import TokenWithItsdangerous  # 因为在里面使用了current_app所以放这里防止循环导入
        token = TokenWithItsdangerous.generate(
            username=user.username,
            email=user.email
        )
        # 使用Redis管道确保这两步插入数据操作的原子性
        pipe = redis_conn.pipeline()
        pipe.set(user.username, token, 360)
        pipe.set(f'{user.username}_flag', '1', 60)
        pipe.execute()
        response = {
            "link": url_for('users.confirm_email', token=token, _external=True),
            "err_msg": err_msg
        }
        return jsonify(response)
    response = {
        "err_msg": err_msg
    }
    return jsonify(response)
